import type { Terminal } from "@xterm/xterm";
import type {
  ClientType,
  SFTPType,
  StreamType,
  FileEntryWithType,
} from "@/utils/client";
import { basename, join, format, normalize, parse, sep } from "node:path";
import { JsZipFolder, JsZipOutput } from "@/utils/file";
import { spawn, exec } from "node:child_process";
import { NewClient } from "@/utils/client";
import { ftpStorage } from "@/storage";
import {
  accessSync,
  constants,
  stat,
  readdir,
  mkdir,
  existsSync,
  createReadStream,
  rm,
  statSync,
} from "node:fs";
import type { Dirent } from "node:fs";
import {
  findTree,
  toFormatTime,
  toLinuxPath,
  toWindowPath,
} from "@/utils/index";
import { emitter } from "@/utils/mitt";
import { unref } from "vue";
import { playMsgByTts } from "@/demos/node";
import log from "electron-log/renderer";
import { shell } from "electron";

function pathExists(path: string) {
  try {
    accessSync(path, constants.F_OK);
    return true; // 路径存在
  } catch (err) {
    return false; // 路径不存在或无法访问
  }
}

/**
 * 获取FtpItem
 */
export const getFtpItem = (id: string) => {
  return findTree(unref(ftpStorage), (f) => f.id === id);
};

/**
 *
 * @param path 打开资源管理器的目录
 */
export const onStartExec = (path: string) => {
  return exec(`start ${path}`);
};

/**
 *
 * @param host 打开资源管理器的目录
 */
export const onStartShellExec = (host: string) => {
  shell.openExternal(host);
};

/**
 *
 * @param path 运行后端服务
 */
export const onDotnetRunExec = (
  item: RunType,
  detached: boolean = false,
  callback?: (type: string, message: string) => void
) => {
  const exists = pathExists(item.source);
  if (exists) {
    let child = spawn(
      process.platform === "win32" ? "cmd" : "/bin/bash",
      ["/c", `cd /d ${item.source} && dotnet ${item.filename}.dll`],
      {
        detached,
      }
    );
    // 子进程退出时清理引用
    child.stdout.on("data", (data) => {
      callback?.("data", data.toString());
    });
    // 监听错误输出
    child.stderr.on("data", (data) => {
      item.running = false;
      child.kill("SIGKILL");
      callback?.("error", `[错误] ${data.toString()}`);
    });
    // 子进程退出时清理引用
    child.on("exit", () => {
      item.running = false;
      console.log("终端已退出");
    });
    return child.pid;
  } else {
    callback?.("error", `发布文件不存在：${item.source}`);
  }
};

/**
 * 杀死进程
 * @param pid
 */
export const onTaskkill = (pid: string | number) => {
  // Windows 下杀进程树（避免残留）
  spawn("taskkill", ["/pid", String(pid), "/f", "/t"]);
};

/**
 *
 * @param path 发布后端服务
 */
export const onDotnetPublishExec = (
  item: RunType,
  callback?: (error: Error | null, stdout: string, stderr: string) => void
) => {
  const pathSegments = parse(item.source).dir.split(sep);
  const targetSegments = pathSegments.slice(
    0,
    pathSegments.indexOf(item.filename) + 1
  );
  const targetPath = join(...targetSegments);
  const formatPathFile = format({
    dir: targetPath,
    name: basename(item.filename),
    ext: ".csproj",
  });
  const exists = pathExists(formatPathFile);
  if (exists) {
    const child = exec(
      `start cmd /k "cd /d ${item.source} && dotnet publish -c Release  ${formatPathFile} && exit"`,
      callback
    );
    return child;
  } else {
    callback?.(new Error(`发布文件不存在：${formatPathFile}`), "", "");
  }
};

/**
 * ftp开始上传
 * @param sftp
 * @param localPath
 * @param remotePath
 */
export const onFastPut = (
  sftp: SFTPType,
  localPath: string,
  remotePath: string
) => {
  return new Promise((resolve, reject) => {
    // 流方法
    const readStream = createReadStream(localPath);
    const writeStream = sftp.createWriteStream(remotePath);
    let totalBytesTransferred = 0;
    readStream.on("data", (chunk) => {
      totalBytesTransferred += chunk.length;
      const totalFileSize = statSync(localPath).size;
      const percentage = (totalBytesTransferred / totalFileSize) * 100;
      const percent = percentage.toFixed(2);
      emitter.emit("showLoading", {
        text: `${basename(localPath)} => 上传进度 ${percent}%`,
      });
      if (Math.floor(percentage) >= 100) {
        // writeStream finish出问题了，不执行，暂时用进度判断上传结束并关闭流通到
        // 关闭本地流
        readStream.close(() => {
          // 关闭远程流
          writeStream.close(() => {
            resolve(true);
          });
        });
      }
    });
    // 监听可写流的 'drain' 事件来追踪已写入的字节
    // writeStream.on('drain', () => {
    //     console.log(`Bytes written: ${writeStream.bytesWritten}`);
    // });
    writeStream.on("finish", () => {
      emitter.emit("hideLoading");
      resolve(true);
    });
    writeStream.on("error", (err: string) => {
      console.error("Error uploading file:", err);
      log.info("onFastPut => FTP上传服务器流错误:", err);
      reject("上传错误");
    });
    readStream.on("error", (err) => {
      log.info("onFastPut => FTP上传本地流错误:", err);
      // 处理错误，可能需要关闭连接
      reject("上传错误");
    });
    readStream.pipe(writeStream);

    // fastPut方法
    // sftp.fastPut(
    //     localPath,
    //     toLinuxPath(remotePath),
    //     (error) => {
    //         if (error) {
    //             reject('上传失败');
    //         } else {
    //             console.log('FastPut complete!');
    //             resolve(true);
    //         }
    //     }
    // );
  });
};

/**
 * 递归上传文件夹函数
 * @param sftp
 * @param localPath
 * @param remotePath
 */
export const onFastPutDir = async (
  sftp: SFTPType,
  localPath: string,
  remotePath: string
) => {
  // 确保文件夹存在
  await new Promise((resolve) => {
    sftp.mkdir(remotePath, (error) => {
      if (error) {
        log.info(`onFastPutDir => ${localPath} 文件路径不存在:`, error);
        resolve(false);
      } else resolve(true);
    });
  });

  const files: Dirent[] = await new Promise((resolve, reject) => {
    readdir(localPath, { withFileTypes: true }, (error, files) => {
      if (error) {
        log.info("onFastPutDir => 上传文件读取本地文件错误:");
        reject("错误2");
      } else resolve(files);
    });
  });

  for (const file of files) {
    const localFilePath = join(localPath, file.name);
    const remoteFilePath = toLinuxPath(join(remotePath, file.name));

    // 当前文件
    emitter.emit("showLoading", { text: remoteFilePath });

    if (file.isDirectory()) {
      // 如果是子文件夹，递归上传
      await onFastPutDir(sftp, localFilePath, remoteFilePath);
    } else {
      // 如果是文件，使用fastPut上传
      await onFastPut(sftp, localFilePath, remoteFilePath);
    }
  }
};

/**
 * 上传文件
 * @param sftp
 * @param path
 * @param files
 */
export const onUpLoadFiles = async (
  path: string,
  files: File[],
  sftp?: SFTPType
) => {
  if (files.length && sftp) {
    try {
      for (let i = 0; i < files.length; i++) {
        const file = files[i];
        const localPath = file.path;
        const remotePath = toLinuxPath(`${path}/${file.name}`);
        const isDirectory = await isStatDirectory(file.path);

        // 当前文件
        emitter.emit("showLoading", { text: remotePath });

        if (isDirectory) {
          await onFastPutDir(sftp, localPath, remotePath);
        } else {
          await onFastPut(sftp, localPath, remotePath);
        }
      }
    } catch (error) {
      log.info(`onUpLoadFiles => ${path} 上传错误:`, error);
      return Promise.reject("上传错误");
    }
  } else {
    log.info(`onUpLoadFiles => SFTP对象不存在 上传错误`);
    return Promise.reject("上传错误");
  }
};

/**
 * ftp开始下载
 * @param sftp
 * @param localPath
 * @param remotePath
 */
export const onFastGet = async (
  sftp: SFTPType,
  remotePath: string,
  localPath: string
) => {
  const lPath = toWindowPath(localPath);
  const isExist = existsSync(lPath);
  if (isExist) {
    await new Promise((resolve, reject) => {
      rm(lPath, (error) => {
        if (error) {
          log.info("onFastGet => 删除文件失败:", error);
          reject(error);
        } else {
          resolve(true);
        }
      });
    });
  }
  await new Promise((resolve, reject) => {
    sftp.fastGet(toLinuxPath(remotePath), lPath, (error) => {
      if (error) {
        log.info("onFastGet => FTP下载失败:", error);
        reject(error);
      } else {
        resolve(true);
      }
    });
  });
};

/**
 * 递归下载文件夹函数
 * @param sftp
 * @param localPath
 * @param remotePath
 */
export const onFastGetDir = async (
  sftp: SFTPType,
  remotePath: string,
  localPath: string
) => {
  // 确保文件夹存在
  await new Promise((resolve) => {
    mkdir(localPath, (error) => {
      if (error) {
        const msg = `onFastGetDir => 本地${localPath}文件夹不存在`;
        log.info(msg);
        resolve(msg);
      } else resolve(true);
    });
  });
  const files: FileEntryWithType[] = await new Promise((resolve, reject) => {
    sftp.readdir(remotePath, (error, files) => {
      if (error) {
        const msg = `onFastGetDir => 服务器${localPath}文件夹不存在`;
        log.info(msg);
        reject(msg);
      } else resolve(files);
    });
  });

  for (const file of files) {
    const localFilePath = toLinuxPath(join(localPath, file.filename));
    const remoteFilePath = toLinuxPath(join(remotePath, file.filename));

    // 当前文件
    emitter.emit("showLoading", { text: localFilePath });

    if (file.attrs.isDirectory()) {
      // 如果是子文件夹，递归上传
      await onFastGetDir(sftp, remoteFilePath, localFilePath);
    } else {
      // 如果是文件，使用fastPut上传
      await onFastGet(sftp, remoteFilePath, localFilePath);
    }
  }
};

/**
 * 下载文件
 * @param remotePath
 * @param localPath
 * @param sftp
 * @param files
 */
export const onDownloadFiles = async (
  remotePath: string,
  localPath: string,
  files: FileEntryWithType[],
  sftp?: SFTPType
) => {
  if (files.length && sftp) {
    try {
      for (let i = 0; i < files.length; i++) {
        const file = files[i];
        const rPath = toLinuxPath(`${remotePath}/${file.filename}`);
        const lPath = toWindowPath(`${localPath}/${file.filename}`);

        // 当前文件
        emitter.emit("showLoading", { text: lPath });

        if (file.attrs.isDirectory()) {
          await onFastGetDir(sftp, rPath, lPath);
        } else {
          await onFastGet(sftp, rPath, lPath);
        }
      }
      return Promise.resolve(true);
    } catch (e) {
      return Promise.reject("下载错误");
    }
  }
};

/**
 * 本地文件是否是文件夹
 * @param localPath
 */
export const isStatDirectory = (localPath: string) => {
  return new Promise((resolve, reject) => {
    stat(localPath, (err, stats) => {
      if (err) {
        log.info("isStatDirectory => 检查文件失败", err);
        reject(err);
      } else {
        resolve(stats.isDirectory());
      }
    });
  });
};

interface ZipPackType {
  output: string;
  size: string;
  name: string;
  target: string;
}

/**
 * 打包成压缩包
 * @param web
 * @param alter 只传5分钟内变更的文件（不包含排除的文件）
 * @param hash
 */
export const onZipPack = ({
  web,
  alter,
  hash,
}: {
  web: WebType | RunType;
  alter?: boolean;
  hash?: boolean;
}): Promise<ZipPackType> => {
  return new Promise((resolve, reject) => {
    const target = join(process.cwd(), "./zips");
    JsZipFolder({ target, web, alter, hash })
      .then(({ output, size, name }) => {
        // 打印成功信息
        console.log(`☀️ ${name} 已压缩完成：`, output, ` （${size}）`);
        // 解压一份
        // JsZipOutput(output).then(() => {
        // })
        resolve({ output, size, name, target });
      })
      .catch(reject);
  });
};

/**
 * 项目发布
 * @param row
 * @param type
 * @param term
 */
export const onBuildPack = (row: WebType, type: string, term?: Terminal) => {
  playMsgByTts(`正在发布，请耐心等待。`);
  return new Promise((resolve, reject) => {
    // 发布目录
    const path = join(row.source, "../");

    // 找到发布根目录
    const dir =
      type === "server" ? join(path, "../../../") : join(row.source, "../");
    const exists = pathExists(dir);
    if (!exists) {
      const msg = `onBuildPack => 发布失败\n${row.source} \n路径不存在`;
      log.info(msg);
      return reject(msg);
    }

    // 创建子进程来执行命令行指令
    let command = "";
    if (type === "server") {
      const formatPathFile = format({
        dir,
        name: basename(dir),
        ext: ".csproj",
      });

      command = `dotnet publish -c Release ${normalize(formatPathFile)}`;
    } else {
      command = `cd /D ${normalize(path)} & ${row.build || "npm run build"}`;
    }

    if (term) {
      term.write("\n\r\n\r");
      term.write(`\x1b[32m开始执行 ${command}\x1b[0m\n\r`);
      term.write("\n\r\n\r");
    }

    const child = spawn(command, {
      shell: true,
      windowsVerbatimArguments: true,
    });

    // 监听标准输出
    child.stdout.on("data", (data) => {
      if (term) term.write(data);
      else console.log(`OUTPUT: ${data}`);
    });

    // 监听标准错误输出
    child.stderr.on("data", (data) => {
      if (term) term.write(data);
      else console.log(`ERROR: ${data}`);
    });

    // 当进程结束时
    child.on("close", () => {
      console.log(`☀️ ${command} 已打包结束。`);
      if (term) {
        term.write("\n\r\n\r");
        term.write(`\x1b[32m执行结束 ${command}\x1b[0m\n\r`);
        term.write("\n\r\n\r");
      }
      resolve(true);
    });

    // 当出现错误
    child.on("error", (err) => {
      const msg = `onBuildPack => 项目发布失败，spawn遇到错误`;
      log.info(msg);
      reject(`执行错误：${err}`);
    });
  });
};

/**
 * 将压缩包（zip）上传到服务器
 * @param row
 * @param ftp
 * @param client
 * @param stream
 * @param type web or server
 */
export const onUploadPack = (
  row: WebType,
  ftp: DeployFtpType,
  client: ClientType,
  stream: StreamType,
  type?: string
) => {
  return new Promise((resolve, reject) => {
    const isWindows = ftp.system === "window";
    client.sftp(async (error, sftp) => {
      if (error) {
        const msg = `onUploadPack => 将压缩包（zip）上传到服务器错误`;
        log.info(msg, error);
        reject();
        throw error;
      }
      if (isWindows) {
        // if (type === 'server') stream.write(`iisreset /stop \r`);
        if (type === "server")
          stream.write(
            `"%windir%\\system32\\inetsrv\\appcmd" stop apppool ${row.appcmd}\r`
          );
        await new Promise((resolve) => setTimeout(resolve, 1000));
        stream.write(`cd /d ${row.target} \r`);
      } else {
        stream.write(`mkdir -p ${row.target} && cd ${row.target} \n`);
      }
      // 获取文件名
      const lastName = basename(row.target);
      const filename = row.filename || lastName;
      // 本地默认压缩包临时文件
      const localTarget = join(process.cwd(), `./zips`);
      // zip 输出路径和文件名
      const output = `${localTarget}/${row.filename || filename}.zip`;
      const target = isWindows
        ? toWindowPath(`${row.target}/${filename}.zip`)
        : toLinuxPath(`${row.target}/${filename}.zip`);
      const current = row.filename ? filename : `../${filename}`;
      onFastPut(sftp, output, target)
        .then(async () => {
          emitter.emit("showLoading", { text: "正在解压中..." });
          if (isWindows) {
            stream.write(
              `"C:\\Program Files\\7-Zip\\7z" x ${target} -y -o${toWindowPath(
                `${row.target}/${filename}`
              )}\r`
            );
            stream.write(`del /q ${current}.zip\r`);
            stream.write("dir\r");
          } else {
            stream.write(`unzip -o ${target} -d ${current}\n`);
            stream.write(`rm -f ./${current}.zip\n`);
            stream.write("ll\n");
          }
          // stream.write(`cd ${current} && ll\n`)
          row.writes.forEach((key) => {
            stream.write(`${key}\n\r`);
          });
          // if (isWindows && type === 'server') stream.write(`iisreset /start \r`);
          if (isWindows && type === "server") {
            await new Promise((resolve) => setTimeout(resolve, 1000));
            stream.write(
              `"%windir%\\system32\\inetsrv\\appcmd" start apppool ${row.appcmd}\r`
            );
          }
          console.log(`☀️ ${target} 上传到服务器成功。`);
          row.time = Date.now();
          row.time_string = toFormatTime(row.time);
          playMsgByTts(`${row.title}，发布已完成。`);
          resolve(true);
        })
        .catch((err) => {
          const msg = `发布失败，错误信息：${err}。`;
          log.info(msg);
          playMsgByTts(msg);
          reject(msg);
        });
    });
  });
};

/**
 * 创建一个新连接
 * @param ftp
 */
export const onNewClient = (
  ftp?: DeployFtpType
): Promise<{ client: ClientType; stream: StreamType }> => {
  return new Promise((resolve, reject) => {
    if (!ftp) return Promise.reject("服务器连接失败");
    const client: ClientType = new NewClient();
    client
      .on("ready", () => {
        client.shell({ term: "xterm" }, (error, stream) => {
          if (error) {
            const msg = `onNewClient => Client初始化失败${error}。`;
            log.info(msg);
            reject(msg);
            throw error;
          }
          resolve({ client, stream });
        });
      })
      .connect(ftp);
  });
};

const onStreamEvent = (
  client: ClientType,
  stream: StreamType,
  resolve: (value: unknown) => void
) => {
  stream
    .on("close", () => {
      // 当stream关闭时，这通常意味着命令执行完毕或连接被关闭
      console.log("Stream :: close");
      client && client.end(); // 结束连接，这也会关闭shell
      resolve(true);
    })
    .on("data", (data: string) => {
      console.log("STDOUT: " + data);
    })
    .stderr.on("data", (data) => {
      log.info("STDERR: " + data);
    });
};

// /**
//  * 静默压缩上传
//  * @param row
//  * @param ftp
//  * @param client
//  * @param stream
//  */
// const silenceUpload = async (
//     row: WebType,
//     ftp: DeployFtpType,
//     client: ClientType,
//     stream: StreamType
// ) => {
//     try {
//         // 压缩
//         await onZipPack(row);
//         // 上传
//         await onUploadPack(row, ftp, client, stream);
//     } catch (e) {
//         const message = e ? String(e) : `\n${row.title} - 上传失败\n`
//         return Promise.reject(message);
//     }
// };
//
// /**
//  * 静默压缩上传
//  * @param row
//  */
// export const onSilenceUpload = (row: WebType) => {
//     return new Promise((resolve, reject) => {
//         const ftpItem = findTree(unref(ftpStorage), item => item.id === row.ftp_id);
//         if (!ftpItem) return reject('该服务器不存在');
//         onNewClient(ftpItem)
//             .then(({client, stream}) => {
//                 onStreamEvent(client, stream, resolve);
//                 silenceUpload(row, ftpItem, client, stream)
//                     .then(() => {
//                         // 退出
//                         stream.end("\nexit\n");
//                     })
//                     .catch(reject);
//             })
//             .catch(reject);
//     });
// };

/**
 * 静默发布压缩上传
 * @param web
 * @param ftp
 * @param client
 * @param stream
 * @param type
 * @param alter 只传5分钟内变更的文件（不包含排除的文件）
 */
const silenceBuildUpload = async (
  web: WebType,
  ftp: DeployFtpType,
  client: ClientType,
  stream: StreamType,
  type: string,
  alter?: boolean | undefined
) => {
  try {
    // 发布
    await onBuildPack(web, type || "web");
    // 压缩
    await onZipPack({ web, alter });
    // 上传
    await onUploadPack(web, unref(ftp), client, stream, type);
  } catch (e) {
    const message = e ? String(e) : `\n${web.title} - 上传失败\n`;
    return Promise.reject(message);
  }
};

/**
 * 静默发布压缩上传
 * @param row
 * @param type
 * @param alter 只传5分钟内变更的文件（不包含排除的文件）
 */
export const onSilenceBuildUpload = (
  row: WebType,
  type: string,
  alter?: boolean | undefined
) => {
  return new Promise((resolve, reject) => {
    const ftpItem = findTree(
      unref(ftpStorage),
      (item) => item.id === row.ftp_id
    );
    if (!ftpItem) return reject("服务器不存在");
    onNewClient(ftpItem)
      .then(({ client, stream }) => {
        onStreamEvent(client, stream, resolve);
        silenceBuildUpload(row, ftpItem, client, stream, type, alter)
          .then(() => {
            // 退出
            stream.end("\nexit\n\r");
          })
          .catch((e) => {
            // 退出
            stream.end("\nexit\n\r");
            const msg = `silenceBuildUpload => 执行错误，${e}`;
            log.info(msg);
            playMsgByTts(msg);
            reject(e);
          });
      })
      .catch((e) => {
        log.info("onSilenceBuildUpload => 执行错误", e);
        reject(e);
      });
  });
};
